﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Threading;
using System.Xml.Linq;

using Perseus;
using Perseus.Data;
using Perseus.Plugins;

using SpeechLib;

using Hetymine.Executioner.Commands;
using Hetymine.Executioner.Plugins;

namespace Hetymine.Executioner {    
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Executioner : Window {      
        #region PInvoke
        /// <summary> The RegisterHotKey function defines a system-wide hot key </summary>
        /// <param name="hwnd">Handle to the window that will receive WM_HOTKEY messages 
        /// generated by the hot key.</param>
        /// <param name="id">Specifies the identifier of the hot key.</param>
        /// <param name="fsModifiers">Specifies keys that must be pressed in combination with the key 
        /// specified by the 'vk' parameter in order to generate the WM_HOTKEY message.</param>
        /// <param name="vk">Specifies the virtual-key code of the hot key</param>
        /// <returns><c>true</c> if the function succeeds; otherwise <c>false</c></returns>
        /// <seealso cref="http://msdn.microsoft.com/en-us/library/ms646309(VS.85).aspx"/>
        [DllImport("user32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);

        [DllImport("user32", SetLastError = true)]
        private static extern int UnregisterHotKey(IntPtr hwnd, int id);

        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        private static extern ushort GlobalAddAtom(string lpString);

        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
        private static extern ushort GlobalDeleteAtom(ushort nAtom);

        private const int WM_HOTKEY = 0x312;
        #endregion
        #region Private Fields
        private static Executioner _Instance; // Internal static instance used to static methods can execute commands
        private static int _ExecutionCount; // To prevent self executing commands from getting stuck in an infinite loop
        public static int _MaxExecutionCount; // The max number of self executing loops before stopping.

        private WindowInteropHelper _Handle; // Interop helper for hooking into messages
        private HwndSource _HwndSource;
        private System.Windows.Forms.NotifyIcon _TrayIcon; // System tray icon (In case hotkeys are lost, need access)
        private System.Windows.Forms.ContextMenu _TrayMenu; 

        // Commands and Hotkeys
        private HotKeyInfo _ShowCommandBox;
        //private List<Hotkey> _HotKeys;
        //private Dictionary<string, List<Hetymine.Executioner.Commands.ICommand>> _Commands;
        
        // History / Autocomplete       
        private List<string> _History = new List<string>(); // Array of command history        
        private int _HistoryIndex = -1; // Current history item index to display (-1 = current, -2 blank)
        private List<string> _AutoComplete = new List<string>(); // Array of possible autocomplete values
        private int _AutoCompleteIndex = 0; // Current index of autocomplete list
        private bool _NoAutoCompleteUpdate = false; // When true, editing text field does not update autocomplete
        private string _CurrentCommand = string.Empty; // Currently typed command that hasnt been executed
        private bool _UpdateCurrent = true; // Update current command on text change?    

        private bool _IsError = false;
        // Timer tp prevent user from holding down a key and executing a command in rapid succession
        private System.Windows.Forms.Timer _HotKeyTimer;

        // Speech Objects
        private bool _SpeechInitialized = false;
        private SpeechLib.SpSharedRecoContext _RecoContext = null;
        private SpeechLib.ISpeechRecoGrammar _Grammar = null;
        private SpeechLib.ISpeechGrammarRule _GrammarRule = null;
        #endregion
        #region Initialization
        public Executioner() {
            InitializeComponent();
            this.Loaded += new RoutedEventHandler(Executioner_Loaded);
            this.Deactivated += new EventHandler(Executioner_Deactivated);
            // Add key handlers for catching enter and command history
            this.txtCommand.PreviewKeyDown += new KeyEventHandler(Command_PreviewKeyDown);
            this.txtCommand.TextChanged += new TextChangedEventHandler(Command_TextChanged);
            this.txtCommand.SelectionChanged += new RoutedEventHandler(Command_SelectionChanged);

            // Disable return so keydown will pick up up and down keys
            this.txtCommand.AcceptsReturn = false;
            
            // Disable input method editor to false so it doesnt start off in non english
            // Temporary solution until localization is added if ever...
            System.Windows.Input.InputMethod.SetIsInputMethodEnabled(this.txtCommand, false);            
        }             
        ~Executioner() {
            this.SaveHistory();
            this.Unload();
        }
        private void Executioner_Loaded(object sender, RoutedEventArgs e) {
            AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);

            this.Visibility = Visibility.Collapsed;

            Executioner._Instance = this;
            Executioner._ExecutionCount = 0;
            Executioner._MaxExecutionCount = 5;

            this._HotKeyTimer = new System.Windows.Forms.Timer();
            this._HotKeyTimer.Interval = 400;
            this._HotKeyTimer.Enabled = false;
            this._HotKeyTimer.Tick += new EventHandler(_HotKeyTimer_Tick);

            // Setup interop to hook into windows messages
            this._Handle = new WindowInteropHelper(this);
            this._HwndSource = HwndSource.FromHwnd(this._Handle.Handle);
            this._HwndSource.AddHook(new HwndSourceHook(this.HookHandler));

            this.PluginService = new PluginService<Plugin>();

            this.ExecutionerCommands = new List<string>();
            this.ExecutionerCommands.AddRange(new string[] { "-clear", "-cmds", "-config", "-exec", "-exit", "-reload" });

            this.Load();

            this.SetupTrayIcons();

            this.LoadHistory();

            if (Executioner.Config.GetBool("Behaviour", "Enable Speech") && this.CommandNames("audio").Count > 0) {
                this.EnableSpeech();
            }
        }
        private void SetupTrayIcons() {
            // Setup tray icon and context menu
            // Uses System.Windows.Forms
            this._TrayIcon = new System.Windows.Forms.NotifyIcon();            
            this._TrayIcon.Icon = new System.Drawing.Icon(
                System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(
                    "Hetymine.Executioner.executioner.ico"
                ),
                16, 16
            );
            this._TrayIcon.Text = "Executioner";
            this._TrayIcon.Visible = true;            
            
            this._TrayMenu = new System.Windows.Forms.ContextMenu();
            this._TrayMenu.MenuItems.Add(new System.Windows.Forms.MenuItem("Reload Commands"));
            this._TrayMenu.MenuItems[0].Click += new EventHandler(TrayMenu_Reload_Click);
            this._TrayMenu.MenuItems.Add(new System.Windows.Forms.MenuItem("Configuration"));
            this._TrayMenu.MenuItems[1].Click += new EventHandler(TrayMenu_Config_Click);
            this._TrayMenu.MenuItems.Add("-");            
            this._TrayMenu.MenuItems.Add(new System.Windows.Forms.MenuItem("Toggle Speech [Disabled]"));
            this._TrayMenu.MenuItems[3].Click += new EventHandler(TrayMenu_ToggleSpeech_Click);
            this._TrayMenu.MenuItems.Add("-");
            this._TrayMenu.MenuItems.Add(new System.Windows.Forms.MenuItem("Website"));
            this._TrayMenu.MenuItems[5].Click += new EventHandler(TrayMenu_Website_Click);
            this._TrayMenu.MenuItems.Add("-");
            this._TrayMenu.MenuItems.Add(new System.Windows.Forms.MenuItem("Exit"));
            this._TrayMenu.MenuItems[7].Click += new EventHandler(TrayMenu_Exit_Click);

            this._TrayIcon.ContextMenu = this._TrayMenu;
        }

        private void LoadCommands() {
            this.HotKeys = new List<HotKey>();
            this.Commands = new Dictionary<string, List<Hetymine.Executioner.Commands.ICommand>>();

            if (!File.Exists(AppDomain.CurrentDomain.BaseDirectory + "commands.xml")) {
                return;
            }

            XDocument xmlDoc = XDocument.Load("commands.xml");
            
            var hotkeys = from hk in xmlDoc.Element("Executioner").Element("HotKeys").Elements()
                          select hk;

            foreach (var hk in hotkeys) {   
                HotKey tmp = new HotKey();
                tmp.Load(hk);
                var check = from mhk in this.HotKeys
                            where mhk.Key == tmp.Key && mhk.Modifiers == tmp.Modifiers
                            select mhk;
              
                // If a hotkey command already exists for this key/modifier
                // Use its existing id                        
                if (check.Count() > 0) {
                    this.HotKeys.Add(tmp);
                    this.HotKeys.Last().Id = check.First().Id;
                }
                else {
                    this.HotKeys.Add(tmp);
                    this.RegisterHotkey(this.HotKeys.Last());
                }
            }
            
            var commands = from cmd in xmlDoc.Element("Executioner").Element("Commands").Elements()
                           select cmd;
                       
            foreach (var cmd in commands) {                
                string type = cmd.Name.LocalName;
                switch (type) {                    
                    case "HotString":
                        HotString hs = new HotString();
                        hs.Load(cmd);
                        this.AddCommand("HotString", hs);
                        break;
                    default:
                        if (type.SafeSubstring(0, 7) == "Plugin.") {
                            type = type.Substring(7);
                            PluginInstance<Plugin> plugin = this.PluginService[type];
                            if (plugin != null && plugin.Instance is CommandPlugin) {
                                Hetymine.Executioner.Commands.ICommand command = ((CommandPlugin)plugin.Instance).InitializeCommand();
                                command.Load(cmd);
                                this.AddCommand(type, command);
                            }
                        }
                        break;                    
                }
            }
        }

        private void LoadHistory() {
            string file = AppDomain.CurrentDomain.BaseDirectory + "history.txt";
            if (File.Exists(file)) {
                string[] history = File.ReadAllLines(file);
                this._History.AddRange(history);
            }
        }
        private void SaveHistory() {
            string file = AppDomain.CurrentDomain.BaseDirectory + "history.txt";
            if (Executioner.Config.GetBool("Behaviour", "Save History", true)) {
                File.WriteAllLines(file, this._History.ToArray(), System.Text.Encoding.UTF8);
            }
            else if (File.Exists(file)) {
                File.Delete(file);
            }
        }

        private void Load() {
            // Load configuration from config.xml
            string config = AppDomain.CurrentDomain.BaseDirectory + "config.ini";
            if (!File.Exists(config)) {
                TextReader tr = new StreamReader(System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(
                    "Hetymine.Executioner.config.ini"
                ));
                string configText = tr.ReadToEnd();
                using (StreamWriter sw = new StreamWriter(config)) {
                    sw.Write(configText);
                }
                
            }
            Executioner.Config = new IniFile(config);            

            Executioner._MaxExecutionCount = Executioner.Config.GetInt("Behaviour", "Max Execution Count", 5).MaxMin(1, 50);
            this._HotKeyTimer.Interval = Math.Max(Executioner.Config.GetInt("Behaviour", "Hot Key Timeout", 400), 250);

            string hotkey = Executioner.Config.GetString("Behaviour", "Command Box Hot Key", "Alt+C");
            this._ShowCommandBox = new HotKeyInfo(hotkey);
            this.RegisterHotkey(this._ShowCommandBox);

            foreach (PluginInstance<Plugin> p in this.PluginService.Plugins.Values) {
                if (p.Instance is ILoader) {
                    ((ILoader)p.Instance).Load();
                }
            }

            // Load commands from commands.cml            
            this.LoadCommands();

            Styles.OuterBorder(this.brdOuter);
            Styles.InnerBorder(this.brdInner);
            Styles.TextBox(this.txtCommand);
        }

        private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) {
            string assemblyName = args.Name.Substring(0, args.Name.IndexOf(",")) + ".dll";

            string path = AppDomain.CurrentDomain.BaseDirectory + Path.DirectorySeparatorChar + "plugins";
            string[] dirs = Directory.GetDirectories(path);
            
            foreach (string d in dirs) {            
                string[] files = Directory.GetFiles(d);                
                foreach (string f in files) {
                    string filename = Path.GetFileName(f);
                    if (filename == assemblyName) {
                        return Assembly.LoadFrom(f);
                    }
                }
            }
            
            return null;
        }
        private void Unload() {            
            foreach (PluginInstance<Plugin> p in this.PluginService.Plugins.Values) {
                if (p.Instance is ILoader) {
                    ((ILoader)p.Instance).Unload();
                }
            }

            foreach (HotKey hk in this.HotKeys) {
                this.UnregisterHotkey(hk);
            }
            this.UnregisterHotkey(this._ShowCommandBox);

            Executioner.Config = null;
        }
        public void Reload() {
            this.Unload();
            this.Load();
        }              

        private void Exit() {
            this._TrayIcon.Dispose();
            System.Windows.Application.Current.Shutdown();
        }
        #endregion
        #region Command Input Handling
        private void Command_PreviewKeyDown(object sender, KeyEventArgs e) {
            this._UpdateCurrent = false;
            switch (e.Key) {
                case Key.Enter: // Execute command!
                    string cmd = this.txtCommand.Text;
                    if (Keyboard.Modifiers == (Keyboard.Modifiers | ModifierKeys.Shift)) {
                        // If there is a space and text selected, it is a history value 
                        // Override to make shift enter replace old value with new
                        int space = cmd.IndexOf(" ", StringComparison.Ordinal);
                        if (space > 0 && txtCommand.SelectionLength > 0) {
                            cmd = cmd.Substring(0, space);
                        }
                        cmd += " " + Clipboard.GetText();
                    }

                    if (cmd.Trim() != string.Empty) {
                        // Remove duplicate commands
                        while (this._History.Contains(cmd)) {
                            this._History.Remove(cmd);
                        }

                        int maxHistory = Executioner.Config.GetInt("Behaviour", "Max History Items", 250);
                        // Do not add exit or clear to histroy
                        if ((maxHistory == -1 || maxHistory > 0) && 
                            cmd != "-clear" && 
                            !cmd.StartsWith("-clear ", StringComparison.Ordinal) && 
                            cmd != "-exit"
                        ) {
                            this._History.Insert(0, cmd);
                            if (maxHistory > 0 && this._History.Count > maxHistory) {
                                this._History.RemoveRange(maxHistory, this._History.Count - maxHistory);
                            }
                        }

                        if (!this.ExecuteCommand(cmd)) {
                            string defaultCommand = Executioner.Config.GetString("Behaviour", "Default Command");
                            if (!this._IsError && defaultCommand != string.Empty) {
                                // If all else fails, run the default command
                                this.ExecuteCommand(defaultCommand + " " + cmd);
                            }
                        }
                    }

                    e.Handled = true;
                    this.Visibility = Visibility.Collapsed;
                    break;
                case Key.Down: // Next command
                    if (this._AutoComplete.Count > 1) {
                        this.AutoCompleteDown();
                    }
                    // If only one item left, set to current command and continue normally
                    else if (this._AutoComplete.Count == 1) {
                        this.ResetCommandAutoComplete();
                        this._HistoryIndex = -1;
                        this.txtCommand.Text = this._CurrentCommand;
                        this.HistoryDown();
                    }
                    else {
                        this.HistoryDown();
                    }
                    e.Handled = true;
                    break;
                case Key.Up: // Previous command                
                    if (this._AutoComplete.Count > 1) {
                        this.AutoCompleteUp();
                    }
                    else if (this._AutoComplete.Count == 1) {
                        this.ResetCommandAutoComplete();
                        this._HistoryIndex = -1;
                        this.HistoryUp();
                    }
                    else {
                        this.HistoryUp();
                    }
                    e.Handled = true;
                    break;
                case Key.Escape: // Hide window on escape
                    this.Visibility = Visibility.Collapsed;
                    break;
                case Key.Delete:
                    this._NoAutoCompleteUpdate = true;
                    break;
                case Key.Back:
                    if (this.txtCommand.SelectionStart > 0) {
                        this._NoAutoCompleteUpdate = true;
                        if (this._AutoComplete.Count > 0) {
                            this.txtCommand.SelectionStart = this.txtCommand.SelectionStart - 1;
                            ++this.txtCommand.SelectionLength;
                        }
                    }
                    break;
            }
            this._UpdateCurrent = true;
        }
        private void Command_TextChanged(object sender, TextChangedEventArgs e) {
            // If not in preview key down
            if (!this._UpdateCurrent) {
                return;
            }
            
            this._HistoryIndex = -1;

            this.ResetCommandAutoComplete();

            if (this._NoAutoCompleteUpdate) {
                this._NoAutoCompleteUpdate = false;
                return;
            }

            if (this.txtCommand.Text != string.Empty) {
                // Get names of all string commands and history
                var names = from h in this._History
                           where h.StartsWith(this.txtCommand.Text, StringComparison.Ordinal)
                           select h;

                var cmds = from c in this.CommandNames("string")
                           where c.StartsWith(this.txtCommand.Text, StringComparison.Ordinal)
                           select c;
               
                names = names.Union(cmds).OrderBy(p => p);

                foreach (string name in names) {                    
                    this._AutoComplete.Add(name);
                }

                // Enable auto complete selection if matching items exist
                if (this._AutoComplete.Count > 0) {
                    string s = this.txtCommand.Text;
                    this._UpdateCurrent = false;
                    this.txtCommand.Text = this._AutoComplete[0];                    
                    this.txtCommand.Select(s.Length, this.txtCommand.Text.Length - s.Length);
                    this._UpdateCurrent = true;
                }
            }
        }
        private void Command_SelectionChanged(object sender, RoutedEventArgs e) {
            if (this._UpdateCurrent) {
                // If not selecting to end of text, clear auto complete
                if (this.txtCommand.SelectionStart + this.txtCommand.SelectionLength != txtCommand.Text.Length) {
                    this.ResetCommandAutoComplete();
                }
                else {
                    foreach (string s in this._AutoComplete) {
                        if (s.Length < this.txtCommand.SelectionStart) {
                            this.ResetCommandAutoComplete();
                            break;
                        }
                    }
                }
            }
        }
        private void Executioner_Deactivated(object sender, EventArgs e) {
            this.Visibility = Visibility.Collapsed;
        }
        private void HistoryUp() {
            if (this._HistoryIndex == -2) {
                this._HistoryIndex = -1;
                this.txtCommand.Text = this._CurrentCommand;
            }
            else if (this._HistoryIndex < this._History.Count - 1) {
                if (this._HistoryIndex == -1) {
                    this._CurrentCommand = this.txtCommand.Text;
                }
                ++this._HistoryIndex;
                this.txtCommand.Text = this._History[this._HistoryIndex];
            }
            else {
                System.Media.SystemSounds.Beep.Play();
                return;
            }

            this.txtCommand.SelectionStart = this.txtCommand.Text.Length;
        }
        private void HistoryDown() {
            // There is a new command typed but not executed and has been cleared
            if (this._HistoryIndex == -2) {
                System.Media.SystemSounds.Beep.Play();
                return;
            }
            else if (this._HistoryIndex == -1) { // Current command index
                // If the current command is not blank, then to blank index (-2)
                if (this.txtCommand.Text != string.Empty) {
                    this._HistoryIndex = -2;
                    this._CurrentCommand = this.txtCommand.Text;
                    this.txtCommand.Text = string.Empty;
                }
                else {
                    System.Media.SystemSounds.Beep.Play();
                    return;
                }
            }
            else if (this._HistoryIndex == 0) { // Set to current command empty or not
                this._HistoryIndex = -1;
                this.txtCommand.Text = this._CurrentCommand;
            }
            else {
                --this._HistoryIndex;
                this.txtCommand.Text = this._History[this._HistoryIndex];
            }

            this.txtCommand.SelectionStart = this.txtCommand.Text.Length;
        }
        private void AutoCompleteUp() {
            if (this._AutoCompleteIndex == -1) {
                ++this._AutoCompleteIndex;                
                int index = this._CurrentCommand.Length;
                this.txtCommand.Text = this._AutoComplete[this._AutoCompleteIndex];
                this.txtCommand.Select(index, this.txtCommand.Text.Length - index);
            }
            else if (this._AutoCompleteIndex < this._AutoComplete.Count - 1) {
                ++this._AutoCompleteIndex;
                int index = this.txtCommand.SelectionStart;
                this.txtCommand.Text = this._AutoComplete[this._AutoCompleteIndex];
                this.txtCommand.Select(index, this.txtCommand.Text.Length - index);
            }
            else {
                System.Media.SystemSounds.Beep.Play();
            }
        }
        private void AutoCompleteDown() {
            if (this._AutoCompleteIndex == -1) {
                System.Media.SystemSounds.Beep.Play();
            }
            else if (this._AutoCompleteIndex == 0) {
                --this._AutoCompleteIndex;
                this._CurrentCommand = this.txtCommand.Text.Substring(0, this.txtCommand.SelectionStart);
                this.txtCommand.Text = string.Empty;
            }
            else {
                --this._AutoCompleteIndex;
                int index = this.txtCommand.SelectionStart;
                this.txtCommand.Text = this._AutoComplete[this._AutoCompleteIndex];
                this.txtCommand.Select(index, this.txtCommand.Text.Length - index);
            }
        }
        #endregion
        #region Tray Menu Events
        private void TrayMenu_Reload_Click(object o, EventArgs e) {
            this.Reload();
        }
        private void TrayMenu_Config_Click(object o, EventArgs e) {
            Hetymine.Executioner.Config config = new Config(this);
            config.Closed += new EventHandler(Config_Closed);
            config.Show();
            this._TrayMenu.MenuItems[1].Enabled = false;            
        }

        private void Config_Closed(object sender, EventArgs e) {
            this._TrayMenu.MenuItems[1].Enabled = true;
            this.Reload();
            if (Executioner.Config.GetBool("Behaviour", "Enable Speech") && this.CommandNames("audio").Count > 0) {
                this.EnableSpeech();
            }
            else {
                this.DisableSpeech();
            }
        }
        private void TrayMenu_ToggleSpeech_Click(object o, EventArgs e) {
            System.Windows.Forms.MenuItem m = o as System.Windows.Forms.MenuItem;

            if (m.Checked == true) {
                DisableSpeech();                
            }
            else {
                EnableSpeech();
            }          
        }
        private void TrayMenu_Website_Click(object o, EventArgs e) {
            System.Diagnostics.Process.Start("http://hetymine.com/executioner/");
        }
        private void TrayMenu_Exit_Click(object o, EventArgs e) {
            this.Exit();
        }
        #endregion
        #region Command Execution
        private bool ExecuteCommand(string command) {
            command = command.Trim();
            string parameters = string.Empty;
            bool found = false;

            // Get all command names and reverse order for greedy 
            // command name in case name has spaces
            List<string> names = this.CommandNames("string");
            names.OrderBy(p => p).Reverse();

            foreach (string n in names) {
                if (command == n || command.StartsWith(n + " ", StringComparison.Ordinal)) {
                    parameters = command.Substring(n.Length).Trim();
                    command = command.Substring(0, n.Length);
                    found = true;
                    break;
                }
            }

            if (found) {
                if (this.ExecuteExecutionerCommand(command, parameters) ||
                    this.ExecuteNamedCommand(command, parameters)
                ) {
                    return true;
                }

                // If the plugin is an executable type then execute it!
                var plugins = from p in this.PluginService.Plugins.Values
                              select p.Instance;
                foreach (Plugin p in plugins) {
                    if (p is IExecutable) {
                        if (p is IMultiInput && !((IMultiInput)p).EnabledInputs.Contains("string")) {
                            continue;
                        }
                        IExecutable e = p as IExecutable;
                        if (e.Name == command) {
                            if (!e.Execute(parameters)) {
                                this._IsError = true;
                            }
                            return true;
                        }
                    }
                    if (p is IExecutableCommands) {
                        IExecutableCommands e = p as IExecutableCommands;
                        if (e.Names.Contains(command)) {
                            if (!e.ExecuteCommand(command, parameters)) {
                                this._IsError = true;
                            }
                            return true;
                        }
                    }
                }
            }
            else {
                // Open url in browser
                if (this.ExecuteUrlCommand(command)) {
                    return true;
                }

                // Try to run it as a process
                if (Executioner.ExecuteProcessCommand(command)) {
                    return true;
                }
            }

            return false;
        }

        private bool ExecuteExecutionerCommand(string command, string parameters) {
            if (command == "-exit") {
                this.Exit();
                return true;
            }
            if (command == "-reload") {
                this.Reload();
                return true;
            }
            // Open commands.xml
            if (command == "-cmds") {
                Executioner.ExecuteProcessCommand(AppDomain.CurrentDomain.BaseDirectory + "\\commands.xml");
                return true;
            }
            if (command == "-config") {
                Hetymine.Executioner.Config config = new Config(this);
                config.Show();
                return true;
            }
            if (command == "-exec") {
                Executioner.ExecuteProcessCommand(AppDomain.CurrentDomain.BaseDirectory);
                return true;
            }

            if (command == "-clear") {
                if (parameters == string.Empty) {
                    this._History = new List<string>();
                }
                else {
                    List<string> newHistory = new List<string>();
                    foreach (string h in this._History) {
                        if (h == parameters || h.StartsWith(parameters + " ", StringComparison.Ordinal)) {
                            continue;
                        }
                        newHistory.Add(h);
                    }
                    this._History = newHistory;
                }
                return true;
            }

            return false;
        }

        private bool ExecuteNamedCommand(string command) {
            return this.ExecuteNamedCommand(command, string.Empty, "string");
        }
        private bool ExecuteNamedCommand(string command, string parameters) {
            return this.ExecuteNamedCommand(command, parameters, "string");
        }
        private bool ExecuteNamedCommand(string command, string parameters, string input) {
            var hotkeys = from cg in this.Commands.Values
                          from c in cg
                          where c.Name == command && (c is HotString)
                          select c;

            bool success = false; // Has a command been executed

            // If no parameters and matches a hotkey, then execute the hotkey            
            if (hotkeys.Count() > 0 && parameters == string.Empty) {
                foreach (var hk in hotkeys) {
                    if (!((IMultiInput)hk).EnabledInputs.Contains(input)) {
                        continue;
                    }
                    success = true;
                    if (!hk.Execute(parameters)) {
                        this._IsError = true;
                    }
                }
            }
            // Otherwise execute all other commands
            else {
                var commands = from cg in this.Commands.Values
                               from c in cg
                               where c.Name == command && !(c is HotString)
                               select c;

                if (commands.Count() > 0) {
                    foreach (var c in commands) {
                        if (c is IMultiInput && !((IMultiInput)c).EnabledInputs.Contains(input)) {
                            continue;
                        }
                        success = true;
                        if (!c.Execute(parameters)) {
                            this._IsError = true;
                        }
                    }
                }
            }            

            return success;
        }

        private bool ExecuteUrlCommand(string command) {
            if (Executioner.Config.GetBool("Behaviour", "Smart Url Parsing")) {
                int index = command.IndexOf("://", StringComparison.Ordinal); // Any protocal
                if (index != -1) {
                    if (index < 1) { return false; } // Shouldn't start as ://

                    return Executioner.ExecuteProcessCommand(command);
                }

                // Local web server
                if (command.StartsWith("localhost", StringComparison.Ordinal)) {
                    return Executioner.ExecuteProcessCommand("http://" + command);  
                }

                // www is most likely a url
                if (command.StartsWith("www.", StringComparison.Ordinal)) {
                    return Executioner.ExecuteProcessCommand("http://" + command);
                }

                // If a space is found, assume not a url
                index = command.IndexOf(" ", StringComparison.Ordinal);
                if (index < 0) {
                    // Urls should have at least 1 period in it
                    index = command.IndexOf(".", StringComparison.Ordinal);
                    if (index > 1) {
                        // Add protocal if non existant
                        if (command.IndexOf("://", StringComparison.Ordinal) > 1) {
                            return Executioner.ExecuteProcessCommand(command);
                        }
                        else {
                            return Executioner.ExecuteProcessCommand("http://" + command);
                        }
                    }
                }
            }
            else {
                string[] protocals = PString.Split(
                    Executioner.Config.GetString("Behaviour", "Url Protocals"),
                    ",",
                    SplitOptions.Trim
                );
                for (int i = 0; i < protocals.Length; i++) {
                    if (!protocals[i].EndsWith("://")) {
                        protocals[i] += "://";
                    }
                }
                if (PString.FindIndexIn(command, protocals) == 0) {
                    return Executioner.ExecuteProcessCommand(command);                    
                }

                string[] endings = PString.Split(
                    Executioner.Config.GetString("Behaviour", "Url Endings"),
                    ",",
                    SplitOptions.Trim
                );
                foreach (string ue in endings) {
                    if (command.EndsWith("." + ue)) {
                        command = "http://" + command;
                    }

                    return Executioner.ExecuteProcessCommand(command);
                }
            }

            return false;
        }
        public static bool ExecuteProcessCommand(string command) {
            return Executioner.ExecuteProcessCommand(command, string.Empty);
        }
        public static bool ExecuteProcessCommand(string command, string arguments) {
            try {
                if (command.StartsWith("@execute:")) {
                    ++Executioner._ExecutionCount;
                    if (Executioner._ExecutionCount > Executioner._MaxExecutionCount) {
                        return false;
                    }

                    command = command.Substring(9);
                    Executioner._Instance.ExecuteCommand(command + " " + arguments);
                }
                else if (arguments != string.Empty) {
                    if (arguments.StartsWith("@display:")) {
                        Display d = new Display();
                        d.Text = System.IO.File.ReadAllText(command, System.Text.Encoding.Default);

                        int t;
                        if (int.TryParse(arguments.SafeSubstring(9), out t)) {
                            t = t.MaxMin(500, 10000);
                            d.Time = t;
                        }

                        d.Show();
                    }
                    else {
                        System.Diagnostics.Process.Start(command, arguments);
                    }
                }
                else {
                    System.Diagnostics.Process.Start(command);
                }

                return true;
            }
            catch {
                System.Media.SystemSounds.Beep.Play();
                System.Media.SystemSounds.Beep.Play();
            }

            return false;
        }       
        #endregion
        #region Speech
        private void EnableSpeech() {
            try {
                if (!this._SpeechInitialized) {                    
                    // Get an insance of RecoContext. I am using the shared RecoContext.
                    this._RecoContext = new SpeechLib.SpSharedRecoContext();
                    // Assign a eventhandler for the Hypothesis Event.
                    //this._RecoContext.Hypothesis += new _ISpeechRecoContextEvents_HypothesisEventHandler(_RecoContext_Hypothesis);
                    // Assign a eventhandler for the Recognition Event.
                    this._RecoContext.Recognition += new _ISpeechRecoContextEvents_RecognitionEventHandler(_RecoContext_Recognition);
                    //Creating an instance of the grammer object.
                    this._Grammar = this._RecoContext.CreateGrammar(0);

                    // http://msdn.microsoft.com/en-us/library/ms720841(VS.85).aspx
                    this._GrammarRule = this._Grammar.Rules.Add(
                        "HotStrings",
                        SpeechRuleAttributes.SRATopLevel | SpeechRuleAttributes.SRADynamic,
                        1
                    );
                    object PropValue = string.Empty;

                    int count = 0;
                    
                    // Clean up and make one loop...
                    var commands = from cg in this.Commands.Values
                                   from c in cg
                                   where c is IMultiInput && ((IMultiInput)c).EnabledInputs.Contains("audio")
                                   select c;

                    foreach (var c in commands) {
                        ++count;
                        this._GrammarRule.InitialState.AddWordTransition(
                            null, // End state of the rule
                            c.Name,
                            " ",
                            SpeechGrammarWordType.SGLexical,
                            c.Name,
                            count,
                            ref PropValue,
                            1.0F
                        );
                    }

                    this._Grammar.Rules.Commit();
                    this._Grammar.CmdSetRuleState("HotStrings", SpeechRuleState.SGDSActive); // SGDSActiveUserDelimited

                    this._SpeechInitialized = true;
                }
                else {
                    this._RecoContext.State = SpeechRecoContextState.SRCS_Enabled;
                }

                this._TrayMenu.MenuItems[3].Checked = true;
                this._TrayMenu.MenuItems[3].Text = "Toggle Speech [Enabled]";
                Executioner.Config.SetBool("Behaviour", "Enable Speech", true);
            }
            catch {
                this._TrayMenu.MenuItems[3].Visible = false;
            }
        }
        private void DisableSpeech() {
            this._TrayMenu.MenuItems[3].Checked = false;
            this._TrayMenu.MenuItems[3].Text = "Toggle Speech [Disabled]";
            Executioner.Config.SetBool("Behaviour", "Enable Speech", false);
            if (this._SpeechInitialized) {
                this._RecoContext.State = SpeechRecoContextState.SRCS_Disabled;
            }
        }
        private void _RecoContext_Recognition(int StreamNumber, object StreamPosition, SpeechRecognitionType RecognitionType, ISpeechRecoResult Result) {
            string command = Result.PhraseInfo.GetText(0, -1, true);            
            this.ExecuteNamedCommand(command, string.Empty, "audio");
        }        
        #endregion
        #region Register and Unregister Hotkeys
        //private const uint ERROR_HOTKEY_ALREADY_REGISTERED = 1409;
        private void RegisterHotkey(HotKeyInfo hotkey) {
            try {
                string atom = this.Dispatcher.Thread.ManagedThreadId.ToString("X8") + 
                    System.Windows.Input.KeyInterop.VirtualKeyFromKey(hotkey.Key);
                hotkey.Id = GlobalAddAtom(atom);                
                if (hotkey.Id == 0) { throw new Exception(); }
                hotkey.Registered = true;

                if (!RegisterHotKey(
                        this._Handle.Handle,
                        hotkey.Id,
                        (uint)hotkey.Modifiers,
                        (uint)System.Windows.Input.KeyInterop.VirtualKeyFromKey(hotkey.Key)
                )) { throw new Exception(); }

                //if (Marshal.GetLastWin32Error() == ERROR_HOTKEY_ALREADY_REGISTERED) { return false; }
                //else { throw new Win32Exception(); }
            }
            catch {
                //MessageBox.Show("Error: " + Marshal.GetLastWin32Error().ToString());
                UnregisterHotkey(hotkey);
                hotkey.Registered = false;
            }
        }
        private delegate void ThreadStart();
        private void UnregisterHotkey(HotKeyInfo hotkey) {                        
            if (hotkey != null && hotkey.Id != 0 && hotkey.Registered == true) {                
                DispatcherOperation dop = this.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart)delegate() {
                    UnregisterHotKey(this._Handle.Handle, hotkey.Id);
                    GlobalDeleteAtom(hotkey.Id);
                });

                // In case it's a reloading, wait for all to be unregistered as to
                // prevent registering an existing hotkey
                dop.Wait();
            }
        }
        private IntPtr HookHandler(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) {
            handled = false;

            if (msg != WM_HOTKEY || this._HotKeyTimer.Enabled == true) {
                return IntPtr.Zero;
            }
            
            ushort id = (ushort)wParam.ToInt32();
            if (this._ShowCommandBox.Id == id) {
                this.ShowCommandBox();
            }
            else { // Regular hotkey
                this._HotKeyTimer.Enabled = true;

                var hotkeys = from hk in this.HotKeys
                              where hk.Id == id
                              select hk;

                foreach (var hk in hotkeys) {
                    hk.ExecuteCommand();
                }
            }
            handled = true;           

            return IntPtr.Zero;
        }
        private void _HotKeyTimer_Tick(object sender, EventArgs e) {
            this._HotKeyTimer.Enabled = false;
        }   
        private void ShowCommandBox() {
            this.ResetCommandInput();

            this.Visibility = Visibility.Visible;

            Point p = Styles.Position(this.ActualWidth, this.ActualHeight);

            this.Left = p.X;
            this.Top = p.Y;

            this.Activate();
            this.txtCommand.Focus();
        }
        #endregion
        #region Properties
        public static IniFile Config { get; set; }
        public PluginService<Hetymine.Executioner.Plugins.Plugin> PluginService { get; protected set; }
        public List<HotKey> HotKeys { get; protected set; }
        public Dictionary<string, List<Hetymine.Executioner.Commands.ICommand>> Commands { get; protected set; }
        private List<string> ExecutionerCommands { get; set; }
        #endregion
        #region Miscellaneous
        private List<string> CommandNames(string input) {
            List<string> names = new List<string>();

            var commands = from cg in this.Commands.Values
                           from c in cg
                           select c;

            foreach (IExecutable c in commands) {
                if (c is IMultiInput && ((IMultiInput)c).EnabledInputs.Contains(input)) {
                    names.Add(c.Name);
                    continue;
                }
                if (input == "string" && !(c is IMultiInput)) {
                    names.Add(c.Name);
                }
            }
            
            var plugins = from p in this.PluginService.Plugins.Values
                          select p.Instance;
            foreach (Plugin p in plugins) {
                if (p is IExecutable) {                    
                    if (p is IMultiInput && ((IMultiInput)p).EnabledInputs.Contains(input)) {
                        names.Add(((IExecutable)p).Name);
                        continue;
                    }
                    if (input == "string" && !(p is IMultiInput)) {
                        names.Add(((IExecutable)p).Name);                        
                    }
                }

                // IExecutableCommands are string only commands
                if (input == "string" && p is IExecutableCommands) {
                    IExecutableCommands e = p as IExecutableCommands;
                    names.AddRange(e.Names);
                }
            }

            if (input == "string") {
                names.AddRange(this.ExecutionerCommands);
            }

            return names;
        }
        private void AddCommand(string type, Hetymine.Executioner.Commands.ICommand command) {
            if (command == null) { return; }

            if (!this.Commands.ContainsKey(type)) {
                this.Commands.Add(type, new List<Hetymine.Executioner.Commands.ICommand>());
            }
            this.Commands[type].Add(command);
        }
        private void ResetCommandInput() {
            this.txtCommand.Text = string.Empty;
            this._CurrentCommand = string.Empty;
            this._HistoryIndex = -1;
            this._IsError = false;
            Executioner._ExecutionCount = 0;
        }
        private void ResetCommandAutoComplete() {
            this._AutoComplete = new List<string>();
            this._AutoCompleteIndex = 0;
        }
        #endregion
    }
}
